﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Linq;
using System.Reflection;

namespace Xceed.Silverlight.Data
{
  internal class DataSourceProviderFactory
  {
    internal virtual DataSourceProvider Create( object itemsSource )
    {
      // No DataSourceProvider can handle a null data source.
      if( itemsSource == null )
        return null;

      // There is nothing to do if the data source is already a DataSourceProvider.
      if( itemsSource is DataSourceProvider )
        return ( DataSourceProvider )itemsSource;

      // The data source is a Wcf Data Services.
      if( itemsSource is DataServiceQuery )
        return DataSourceProviderFactory.CreateWcfDataServicesDataSourceProvider( ( DataServiceQuery )itemsSource, null );

      // Analyze the itemsSource in case it is an Array that contains both
      // the DataServiceQuery and the DataServiceContext.
      DataSourceProvider dataSourceProvider;
      if( DataSourceProviderFactory.HandleWcfDataServicesMultiParameters( itemsSource, out dataSourceProvider ) )
        return dataSourceProvider;

      // The data source is a data list.
      if( itemsSource is IEnumerable )
        return new ListDataSourceProvider( ( IEnumerable )itemsSource );

      // The data source is of unknown type.
      return null;

    }

    private static WcfDataServicesDataSourceProviderBase<TElement> CreateWcfDataServicesDataSourceProvider<TElement>(
      DataServiceQuery<TElement> dataServiceQuery,
      DataServiceContext dataServiceContext )
    {
      return ( dataServiceContext != null )
               ? new WcfDataServicesDataSourceProvider<TElement>( dataServiceQuery, dataServiceContext )
               : new WcfDataServicesDataSourceProvider<TElement>( dataServiceQuery );
    }

    private static WcfDataServicesDataSourceProviderBase CreateWcfDataServicesDataSourceProvider(
      DataServiceQuery dataServiceQuery,
      DataServiceContext dataServiceContext )
    {
      if( dataServiceQuery == null )
        throw new ArgumentNullException( "dataServiceQuery" );

      Type genericDataServiceQueryType = typeof( DataServiceQuery<> );
      genericDataServiceQueryType = genericDataServiceQueryType.MakeGenericType( dataServiceQuery.ElementType );

      if( genericDataServiceQueryType.IsAssignableFrom( dataServiceQuery.GetType() ) )
      {
        MethodInfo currentMethodInfo = ( MethodInfo )MethodBase.GetCurrentMethod();

        foreach( MethodInfo methodInfo in currentMethodInfo.ReflectedType.GetMethods( BindingFlags.Static | BindingFlags.NonPublic ) )
        {
          if( ( methodInfo.IsGenericMethod ) && ( methodInfo.Name == currentMethodInfo.Name ) )
          {
            MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod( dataServiceQuery.ElementType );
            object dataSourceProvider = genericMethodInfo.Invoke( null, new object[] { dataServiceQuery, dataServiceContext } );

            return dataSourceProvider as WcfDataServicesDataSourceProviderBase;
          }
        }
      }

      return ( dataServiceContext != null )
               ? new WcfDataServicesDataSourceProvider( dataServiceQuery, dataServiceContext )
               : new WcfDataServicesDataSourceProvider( dataServiceQuery );
    }

    private static bool HandleWcfDataServicesMultiParameters(
      object itemsSource,
      out DataSourceProvider dataSourceProvider )
    {
      dataSourceProvider = null;

      Type itemsSourceType = itemsSource.GetType();
      IEnumerable<object> parameters = null;

      if( itemsSourceType.IsArray )
      {
        // The array must be able to contain objects of different types.
        if( typeof( object ) == itemsSourceType.GetElementType() )
        {
          object[] items = itemsSource as object[];

          parameters = items;
        }
      }
      else if( typeof( IEnumerable ).IsAssignableFrom( itemsSourceType ) )
      {
        // The enumerable must be able to contain objects of different types.
        if( !itemsSourceType.IsGenericType )
        {
          parameters = ( ( IEnumerable )itemsSource ).Cast<object>();
        }
        else if( ( itemsSourceType.GetGenericArguments().Length > 0 )
              && ( typeof( object ) == ( itemsSourceType.GetGenericArguments()[ 0 ] ) ) )
        {
          parameters = itemsSource as IEnumerable<object>;

          if( parameters == null )
          {
            parameters = ( ( IEnumerable )itemsSource ).Cast<object>();
          }
        }
      }

      if( parameters != null )
        return DataSourceProviderFactory.HandleWcfDataServicesMultiParameters( parameters, out dataSourceProvider );

      return false;
    }

    private static bool HandleWcfDataServicesMultiParameters(
      IEnumerable<object> itemsSource,
      out DataSourceProvider dataSourceProvider )
    {
      dataSourceProvider = null;

      if( itemsSource == null )
        return false;

      // Retrieve the first two values of the source.
      object firstValue = null;
      object secondValue = null;
      IEnumerator<object> enumerator = itemsSource.GetEnumerator();

      if( enumerator.MoveNext() )
      {
        firstValue = enumerator.Current;
      }

      if( enumerator.MoveNext() )
      {
        secondValue = enumerator.Current;
      }

      // Find out if one of the two values is a DataServiceQuery.
      DataServiceQuery dataServiceQuery = firstValue as DataServiceQuery;
      DataServiceContext dataServiceContext = secondValue as DataServiceContext;

      // The first value isn't a DataServiceQuery.  Find out if the second value is one.
      if( dataServiceQuery == null )
      {
        dataServiceQuery = secondValue as DataServiceQuery;
        dataServiceContext = firstValue as DataServiceContext;
      }

      // A DataServiceQuery was found amoung the data source.
      if( dataServiceQuery != null )
      {
        dataSourceProvider = DataSourceProviderFactory.CreateWcfDataServicesDataSourceProvider(
                               dataServiceQuery,
                               dataServiceContext );
      }

      return ( dataSourceProvider != null );
    }
  }
}
